package cli.command;

import com.sun.xml.internal.ws.api.model.CheckedException;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.UncheckedIOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.HashSet;
import java.util.function.Consumer;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

public class Find implements Command {
    private PrintWriter out;
    private Path currentPath;
    private Path startPath; // 搜索路径的起点
    private Pattern name = Pattern.compile(".*"); // 正则表达式
    private boolean onlyRegularFile = false; // 必须是文件
    private boolean onlyDirectory = false; // 必须是文件夹
    private int maxDepth = Integer.MAX_VALUE; // 最大搜索深度
    private long minSize = Long.MIN_VALUE; // 大小的下限（字节）
    private long maxSize = Long.MAX_VALUE; // 大小的上限（字节）
    private LocalDate minDate = LocalDate.MIN; // 时间的下限
    private LocalDate maxDate = LocalDate.MAX; // 时间的上限

    public Find(PrintWriter out, Path currentPath) {
        this.out = out;
        this.currentPath = currentPath;
    }

    @Override
    public void handle(String[] args) {
        int pos = 0;
        startPath = currentPath;
        if (pos < args.length && !args[pos].startsWith("-")) {
            try {
                startPath = currentPath.resolve(args[pos]);
                pos++;
            } catch (InvalidPathException e) {
                out.println("无法识别路径"+args[pos]+"！");
                return;
            }
        }
        while (pos < args.length) {
            String key = args[pos++];
            Consumer<String> parameter = findParameter(key);
            if (parameter == null) {
                out.println("无法识别参数"+key+"！");
                return;
            }
            if (pos >= args.length) {
                out.println("缺少"+key+"的参数值！");
                return;
            }
            try {
                parameter.accept(args[pos++]);
            } catch (Exception e) {
                return;
            }
        }
        try {
            Files.walkFileTree(startPath, new HashSet<>(), maxDepth, new FileFinder());
        } catch (IOException e) {
            out.println("查找失败！");
        }
    }

    private class FileFinder implements FileVisitor<Path> {
        @Override
        public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
            if (validate(dir, attrs)) {
                out.println(startPath.relativize(dir));
            }
            return FileVisitResult.CONTINUE;
        }

        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
            if (validate(file, attrs)) {
                out.println(startPath.relativize(file));
            }
            return FileVisitResult.CONTINUE;
        }

        @Override
        public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
            if (Files.isDirectory(file)) {
                out.println("无法访问文件夹" + startPath.relativize(file) + "!");
            } else {
                out.println("无法访问文件" + startPath.relativize(file) + "!");
            }
            return FileVisitResult.SKIP_SUBTREE;
        }

        @Override
        public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
            return FileVisitResult.CONTINUE;
        }
    }

    private boolean validate(Path path, BasicFileAttributes attrs) {
        boolean valid = true;
        if (path.getFileName() != null)
            valid &= path.getFileName().toString().matches(name.pattern());
        valid &= !onlyRegularFile || attrs.isRegularFile();
        valid &= !onlyDirectory || attrs.isDirectory();
        valid &= minSize <= attrs.size();
        valid &= attrs.size() <= maxSize;
        LocalDate date = attrs.lastModifiedTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
        valid &= minDate.isBefore(date) || minDate.isEqual(date);
        valid &= date.isBefore(maxDate) || date.isEqual(date);
        return valid;
    }

    private Consumer<String> findParameter(String key) {
        switch (key) {
            case "-name":
                return this::putName;
            case "-type":
                return this::putType;
            case "-maxdepth":
                return this::putMaxDepth;
            case "-size":
                return this::putSize;
            case "-mtime":
                return this::putModifyTime;
            default:
                return null;
        }
    }

    private void putName(String value) {
        try {
            name = Pattern.compile(value);
        } catch (PatternSyntaxException e) {
            out.println("正则表达式语法错误！");
            throw e;
        }
    }

    private void putType(String value) {
        switch (value) {
            case "f":
                onlyRegularFile = true;
                break;
            case "d":
                onlyDirectory = true;
                break;
            default:
                out.println("请输入正确的文件类型！");
                throw new RuntimeException();
        }
    }

    private void putMaxDepth(String value) {
        try {
            maxDepth = Integer.parseUnsignedInt(value);
        } catch (NumberFormatException e) {
            out.println("请输入一个正整数！");
            throw e;
        }
    }

    private void putSize(String value) {
        try {
            long l = Long.parseLong(value);
            if (l > 0) {
                minSize = l;
            } else if (l == 0) {
                minSize = 0;
                maxSize = 0;
            } else {
                maxSize = Math.abs(l);
            }
        } catch (NumberFormatException e) {
            out.println("请输入一个整数！");
            throw e;
        }
    }

    private void putModifyTime(String value) {
        try {
            long l = Long.parseLong(value);
            if (l > 0) {
                maxDate = LocalDate.now().minusDays(l);
            } else if (l == 0) {
                maxDate = LocalDate.now();
                minDate = LocalDate.now();
            } else {
                minDate = LocalDate.now().minusDays(Math.abs(l));
            }
        } catch (NumberFormatException e) {
            out.println("请输入一个整数！");
            throw e;
        }
    }
}
